The symmetry-exceptional slopes of K=o9_30634 are (-2,1), (-1,1), and (2,1). All three have symmetry group Z/2+Z/2 and thus they can be written in three different ways as DBC. Here we determine all these three ways. Since K is strongly invertible all slopes can be written in one way as DBC branched along a link L in S3. That means for the three symmetry-exceptional fillings we need to find another 2 ways to write them as DBCs.

In [2]:

    import time
    import snappy
    import pandas
    DBChomologies = pandas.read_csv("DBChomologies_non_alternating.csv") # We load a list of all non alternating links in
    #the HTLinkExteriors together with their homologies (if the homology is cyclic) and their crossing numbers.
    ### REMARK: To build the list we need to run the code in the file Build_DBChomologies which takes around 60 minutes.
    #Here we just load it.

    def double_branched_cover(link):
        """
        Returns the double branched cover of the link.
        """
        L=link.copy()
        for i in range(L.num_cusps()):
            L.dehn_fill((2,0),i)
        for cov in L.covers(2):
            if (2.0, 0.0) not in cov.cusp_info('filling'):
                return cov

    def better_is_isometric_to(X,Y,index):
        """
        Returns True if X and Y are isometric.
        Returns False if X and Y have different homologies. TO DO: Use volume to rigorously distinguish X and Y.
        Returns 'unclear' if SnapPy cannot verify it.
        The higher the index the harder SnapPy tries.
        """
        w='unclear'
        if X.homology()!=Y.homology():
            w=False
        if w=='unclear':
            for i in (0,index):
                try:
                    w=X.is_isometric_to(Y)
                except RuntimeError:
                    pass
                except snappy.SnapPeaFatalError:
                    pass
                if w==True:
                    break
                if w==False:
                    w='unclear'
                X.randomize()
                Y.randomize()
                i=i+1
        return w

    def possible_DBC(homologies,max_crossings=15):
        """
        Takes a list of orders of homologies and returns a list consisting of all DBC of alternating links in the HT link table with that homologies together with the link names.
        """
        DBCList=[]
        LINKS=[]
        for order in homologies:
            LINKS=LINKS+DBChomologies.loc[(DBChomologies['homology']==order) & (DBChomologies['crossings']<=max_crossings)]['knot'].tolist()
        for link in LINKS:
            L=snappy.Manifold(link)
            D=double_branched_cover(L)
            DBCList.append([D,link])
        return DBCList

    ### The following two functions are written by Dunfield and search for positive triangulations.

    def all_positive(manifold):
        return manifold.solution_type() == 'all tetrahedra positively oriented'

    def find_positive_triangulation(manifold, tries=100):
        M = manifold.copy()
        for i in range(tries):
            if all_positive(M):
                return M
            M.randomize()
        for d in M.dual_curves():
            X = M.drill(d)
            X = X.filled_triangulation()
            X.dehn_fill((1,0))
            for i in range(tries):
                if all_positive(X):
                    return X
                X.randomize()

        # In the closed case, here is another trick.
        if all(not c for c in M.cusp_info('is_complete')):
            for i in range(tries):
                # Drills out a random edge
                X = M.__class__(M.filled_triangulation())
                if all_positive(X):
                    return X
                M.randomize()

    def better_find_positive_triangulation(M,tries=1):
        '''
        Search for a positive triangulation, but ignores errors.
        '''
        RandomizeCount=0
        while RandomizeCount<tries:
            try:
                X=find_positive_triangulation(M)
                return X
            except snappy.SnapPeaFatalError:
                M.randomize()
                RandomizeCount=RandomizeCount+1
        return None

    def is_alternating(knot,slope,try_hard=False,index=10,tries=1,max_cro=15):
        '''
        Checks if the slope is alternating.
        '''
        K=snappy.Manifold(knot)
        K.dehn_fill(slope)
        DBC=possible_DBC([K.homology().order()],max_crossings=max_cro)
        for D in DBC:
            w=better_is_isometric_to(D[0],K,index)
            if w==True:
                return [slope,D[1]]
        if try_hard:
            X=better_find_positive_triangulation(K,tries)
            if X is not None:
                for D in DBC:
                    Y=better_find_positive_triangulation(D[0],tries)
                    if Y is not None:
                        w=better_is_isometric_to(X,Y,index)
                        if w==True:
                            return [slope,D[1]]
        return False

In [2]:

    K=snappy.Manifold('o9_30634')
    exc_sym_slopes=[(-2,1),(-1,1),(2,1)]
    for s in exc_sym_slopes:
        K.dehn_fill(s)
        print(s,K.symmetry_group())

    (-2, 1) Z/2 + Z/2
    (-1, 1) Z/2 + Z/2
    (2, 1) Z/2 + Z/2

In [4]:

    start_time = time.time()
    branching_sets=[]
    knot='o9_30634'
    for slope in exc_sym_slopes:
        w=is_alternating(knot,slope,index=25,max_cro=15)
        if w!=False:
            branching_sets.append([knot,w[0],w[1]])
            print('We found a branching set:',knot,slope,w[1])

    print('Total number of branching sets we have found:', len(branching_sets))
    print('Total time taken: %s minutes ---' % ((time.time() - start_time)/60))

    We found a branching set: o9_30634 (-1, 1) K13n2148
    Total number of branching sets we have found: 1
    Total time taken: 0.8800097187360127 minutes ---

Next we will search for branching sets in more general manifolds. For that we will take links in the HT link tables fill some one component of it to get a surgery diagram of a link in a manifold. Then we take the double branched cover of that link and search for a match with a symmetric filling on A.

In [5]:

    DBChomologies_branching = pandas.read_csv("DBChomologies_one_filling.csv")
    ### REMARK: To build the list we need to run the code in the file Build_DBChomologies which takes around 3 days. Here we just load it.

    def double_branched_cover(link):
        """
        Returns the double branched covers of the link. This works also for links in a more general manifold.
        Note that a knot in a more general manifold may have more than one double branched cover
        (or no double branched cover at all if the knot represents a primitive element in homology).
        This function will return the complete list of all double branched covers of the link.
        """
        L=link.copy()
        for i in range(L.num_cusps()):
            if L.cusp_info(i).filling==(0.0, 0.0):
                L.dehn_fill((2,0),i)
        return [cov for cov in L.covers(2) if (2.0, 0.0) not in cov.cusp_info('filling')]

    def possible_DBC_surgery_diagrams(homologies,max_crossings=15):
        """
        Reads off the possible surgery diagrams.
        """
        DBCList=[]
        LINKS=[]
        CUSPS=[]
        SLOPES_STRINGS=[]
        for order in homologies:
            LINKS=LINKS+DBChomologies_branching.loc[(DBChomologies_branching['homology']==order) & (DBChomologies_branching['crossings']<=max_crossings)]['knot'].tolist()
            CUSPS=CUSPS+DBChomologies_branching.loc[(DBChomologies_branching['homology']==order) & (DBChomologies_branching['crossings']<=max_crossings)]['cusp'].tolist()
            SLOPES_STRINGS=SLOPES_STRINGS+DBChomologies_branching.loc[(DBChomologies_branching['homology']==order) & (DBChomologies_branching['crossings']<=max_crossings)]['filling'].tolist()
        SLOPES=[]
        for string in SLOPES_STRINGS:
            string_without_brackets=string[1:-1]
            SLOPES.append(tuple(map(int, string_without_brackets.split(', '))))
        for i in range(0,len(LINKS)):
            DBCList.append([LINKS[i],SLOPES[i],CUSPS[i]])
        return DBCList

    def search_for_branching_set(knot,slope,try_hard=False,index=10,tries=1,max_crossings=15):
        '''
        Searchs for a surgery diagram of the rbanching set
        '''
        K=snappy.Manifold(knot)
        K.dehn_fill(slope)
        DBC=possible_DBC_surgery_diagrams([K.homology().order()],max_crossings)
        for DIAGRAM in DBC:
            L=snappy.Manifold(DIAGRAM[0])
            L.dehn_fill(DIAGRAM[1],DIAGRAM[2])
            for D in double_branched_cover(L):
                w=better_is_isometric_to(D,K,index)
                if w==True:
                    return [[knot,slope,DIAGRAM[0],DIAGRAM[1],DIAGRAM[2]]]
        if try_hard:
            X=better_find_positive_triangulation(K,tries)
            if X is not None:
                for DIAGRAM in DBC:
                    L=snappy.Manifold(DIAGRAM[0])
                    L.dehn_fill(DIAGRAM[1],DIAGRAM[2])
                    for D in double_branched_cover(L):
                        Y=better_find_positive_triangulation(D,tries)
                        if Y is not None:
                            w=better_is_isometric_to(X,Y,index)
                            if w==True:
                                return [[knot,slope,DIAGRAM[0],DIAGRAM[1],DIAGRAM[2]]]
        return False

    def search_for_three_branching_sets(knot,slope,try_hard=False,index=10,tries=1,max_crossings=15):
        '''
        Searchs for a surgery diagram of the branching set
        '''
        BRANCHING_SETS=[]
        K=snappy.Manifold(knot)
        K.dehn_fill(slope)
        homologies=[]
        DBC=possible_DBC_surgery_diagrams([K.homology().order()],max_crossings)
        for DIAGRAM in DBC:
            L=snappy.Manifold(DIAGRAM[0])
            L.dehn_fill(DIAGRAM[1],DIAGRAM[2])
            for D in double_branched_cover(L):
                w=better_is_isometric_to(D,K,index)
                if w==True:
                    BRANCHING_SETS=BRANCHING_SETS+[[knot,slope,DIAGRAM[0],DIAGRAM[1],DIAGRAM[2]]]
                    for i in range(L.num_cusps()):
                        if L.cusp_info(i).filling==(0.0, 0.0):
                            L.dehn_fill((1,0),i)
                    if L.homology().order() not in homologies:
                        homologies.append(L.homology().order())
                    if len(homologies)==3:
                        return BRANCHING_SETS
        if try_hard:
            X=better_find_positive_triangulation(K,tries)
            if X is not None:
                for DIAGRAM in DBC:
                    L=snappy.Manifold(DIAGRAM[0])
                    L.dehn_fill(DIAGRAM[1],DIAGRAM[2])
                    for D in double_branched_cover(L):
                        Y=better_find_positive_triangulation(D,tries)
                        if Y is not None:
                            w=better_is_isometric_to(X,Y,index)
                            if w==True:
                                BRANCHING_SETS=BRANCHING_SETS+[[knot,slope,DIAGRAM[0],DIAGRAM[1],DIAGRAM[2]]]
                                for i in range(L.num_cusps()):
                                    if L.cusp_info(i).filling==(0.0, 0.0):
                                        L.dehn_fill((1,0),i)
                                if L.homology().order() not in homologies:
                                    homologies.append(L.homology().order())
                                if len(homologies)==3:
                                    return BRANCHING_SETS
        return BRANCHING_SETS

In [6]:

    start_time = time.time()
    knot='o9_30634'
    for slope in exc_sym_slopes:
        w=search_for_three_branching_sets(knot,slope,index=3,max_crossings=15)
        if w==[]:
            w=False
        if w!=False:
            branching_sets=branching_sets+w
            print('We found a branching set:',w)

    print('Total number of branching sets we have found:', len(branching_sets))
    print('Total time taken: %s minutes ---' % ((time.time() - start_time)/60))

    We found a branching set: [['o9_30634', (-2, 1), 'L12n1012', (-8, 3), 0], ['o9_30634', (-2, 1), 'L14n23888', (-2, 1), 0], ['o9_30634', (-2, 1), 'L14n24646', (-2, 1), 0]]
    We found a branching set: [['o9_30634', (-1, 1), 'L10n45', (-5, 1), 0], ['o9_30634', (-1, 1), 'L10n49', (3, 4), 0], ['o9_30634', (-1, 1), 'L11n141', (-3, 1), 0], ['o9_30634', (-1, 1), 'L14a20418', (3, 1), 0], ['o9_30634', (-1, 1), 'L14a20418', (3, 1), 1]]
    We found a branching set: [['o9_30634', (2, 1), 'L13n9873', (-2, 1), 1]]
    Total number of branching sets we have found: 10
    Total time taken: 164.701756131649 minutes ---

In [7]:

    branching_sets

Out[7]:

    [['o9_30634', (-1, 1), 'K13n2148'],
     ['o9_30634', (-2, 1), 'L12n1012', (-8, 3), 0],
     ['o9_30634', (-2, 1), 'L14n23888', (-2, 1), 0],
     ['o9_30634', (-2, 1), 'L14n24646', (-2, 1), 0],
     ['o9_30634', (-1, 1), 'L10n45', (-5, 1), 0],
     ['o9_30634', (-1, 1), 'L10n49', (3, 4), 0],
     ['o9_30634', (-1, 1), 'L11n141', (-3, 1), 0],
     ['o9_30634', (-1, 1), 'L14a20418', (3, 1), 0],
     ['o9_30634', (-1, 1), 'L14a20418', (3, 1), 1],
     ['o9_30634', (2, 1), 'L13n9873', (-2, 1), 1]]

Since the knot K is strongly invertible we have always at least on way to write the knot as a DBC over a link in S3. by checking that the above surgery links always have the filling knot an unknot we see that we can write the (-1,1)-and the (-2,1)-filling in two more ways as DBC over a link in a lens space. (These two ways are distinguished by the homology of the lens space. This shows that these two slopes can only be written in a single way as the DBC over a link in S3. For the (2,1)-filling we found a surgery description along a link in RP3. Here we need to find another way to write it as the DBC. For that we search for surgery descriptions along symmetric links.

In [29]:

    def better_is_isometric_to(X,Y,index=10,return_isometries=False):
        """
        Returns True if X and Y are isometric.
        Returns False if it is unclear.
        """
        w=False
        for i in (0,index):
            try:
                w=X.is_isometric_to(Y,return_isometries)
                return w
            except (RuntimeError, snappy.SnapPeaFatalError):
                X.randomize()
                Y.randomize()
                i=i+1
        return w

    def getting_surgery_diagrams(M,max_diagrams=1,max_drill_range=100,max_drill_depth=15,symmetric=False,invertible=False,DIAGRAMS=[]):
        '''
        Returns surgery diagrams of the input manifold.
        max_diagrams is the number of diagrams that is searched for.
        If the flag symmetric is set, only symmetric surgery diagrams gets returned.
        If the flag invertible is set, only surgery diagrams along strongly invertible links gets returned.
        max_drill_range gives the range of geodesics that are drilled (this should be large if searched for invertible).
        max_drill_depth gives the number of components of the surgery diagrams
        (this should be large if searched for symmetric).
        '''
        for i in range(min(len(M.dual_curves()),max_drill_range)):
            Mi=M.drill(i)
            if Mi in snappy.HTLinkExteriors():
                MID=Mi.identify()[-1]
                if MID.name()[0]=='o':
                    MID=Mi.identify()[-2]
                DIAG=getting_surgery_coefficients(Mi,MID)
                if invertible==False and symmetric==False:
                    DIAGRAMS=DIAGRAMS+DIAG
                if invertible:
                    for D in DIAG:
                        link=D.copy()
                        link.dehn_fill([(0,0)] * link.num_cusps())
                        S=better_symmetry_group(link)[0]
                        if S!='unclear':
                            if is_strongly_invertible_link(S):
                                DIAGRAMS=DIAGRAMS+[[D,'invertible']]
                if symmetric:
                    for D in DIAG:
                        if is_symmetric(D)==True:
                            DIAGRAMS=DIAGRAMS+[[D,'symmetric']]
                if len(DIAGRAMS)>=max_diagrams:
                    return DIAGRAMS
            if len(Mi.name())>max_drill_depth:
                return DIAGRAMS+[]
        for i in range(min(len(M.dual_curves()),max_drill_range)):
            Mi=M.drill(i)
            DIAGRAMS=getting_surgery_diagrams(Mi,max_diagrams,max_drill_range,max_drill_depth,symmetric,invertible,DIAGRAMS)
            if len(DIAGRAMS)>=max_diagrams:
                return DIAGRAMS
        return DIAGRAMS+[]

    def getting_surgery_coefficients(Mi,MID):
        '''
        Gives the surgery diagrams.
        '''
        surgerydescriptions=[]
        isoms=better_is_isometric_to(Mi,MID,return_isometries=True)
        if isoms==False:
            return []
        for isom in isoms:
            maps = isom.cusp_maps()
            cusps = isom.cusp_images()
            meridimages = [mat.column(0) for mat in maps] # pulls off first columns of matrices of action, we want inital one to be (1,0) or (-1,0)
            dfslopes = [y for x,y in sorted(zip(cusps,meridimages))]  #reorders slopes according to map of cusps
            surgdesc=snappy.Manifold(MID.name())
            surgdesc.dehn_fill(dfslopes)
            surgerydescriptions.append(surgdesc)
        return surgerydescriptions

    def better_symmetry_group(M,index=10):
        '''
        Computes the symmetry group.
        '''
        randomizeCount=0
        while randomizeCount<index:
            try:
                S=M.symmetry_group()
                full=S.is_full_group()
                return (S,full)
            except (ValueError, RuntimeError, snappy.SnapPeaFatalError):
                M.randomize()
                randomizeCount=randomizeCount+1
        return ('unclear',False)

    def is_strongly_invertible_link(symmetry_group_of_link):
        '''
        Checks if a link is strongly invertible.
        '''
        S=symmetry_group_of_link
        ISOM=S.isometries()
        for I in ISOM:
            if I.extends_to_link():
                if I.cusp_images()==list(range(I.num_cusps())):
                    if I.cusp_maps()==[matrix([[-1,0],[0,-1]])] * I.num_cusps():
                        return True
        return False

    def is_symmetric(surgery_diagram):
        '''
        Checks if the given surgery diagram is symmetric.
        '''
        L=surgery_diagram.copy()
        D=surgery_diagram.copy()
        D.dehn_fill([(0,0)] * D.num_cusps())
        S=better_symmetry_group(D)[0]
        if S=='unclear':
            return False
        ISOM=S.isometries()
        for I in ISOM:
            if I.extends_to_link():
                cusp_check=[]
                for i in range(I.num_cusps()):
                    if (I.cusp_images()[i]==i and I.cusp_maps()[i]!=matrix([[-1,0],[0,-1]])):
                        break
                    if I.cusp_images()[i]!=i:
                        if I.cusp_maps()[i]!=I.cusp_maps()[I.cusp_images()[i]]:
                            break
                        if I.cusp_maps()[i] not in [matrix([[1,0],[0,1]]), matrix([[-1,0],[0,-1]])]:
                            break
                        if I.cusp_maps()[I.cusp_images()[i]] not in [matrix([[1,0],[0,1]]), matrix([[-1,0],[0,-1]])]:
                            break
                        if L.cusp_info()[I.cusp_images()[i]].filling not in [L.cusp_info()[i].filling,(-L.cusp_info()[i].filling[0],-L.cusp_info()[i].filling[1])]:
                            break
                    cusp_check.append(i)
                if len(cusp_check)==I.num_cusps():
                    return True
        return False

In [30]:

    K

Out[30]:

    o9_30634(2,1)

In [31]:

    diag=getting_surgery_diagrams(K,max_diagrams=10,symmetric=True)

In [32]:

    diag

Out[32]:

    [[L13n9734(0,-1)(4,1)(-2,1), 'symmetric'],
     [L13n9734(0,1)(-4,-1)(2,-1), 'symmetric'],
     [L13n10114(0,-1)(0,-1)(0,-1), 'symmetric'],
     [L13n10114(0,1)(0,1)(0,1), 'symmetric'],
     [L12n2176(1,4)(4,-1)(-5,-2)(3,2), 'symmetric'],
     [L12n2176(1,4)(4,-1)(-3,-2)(5,2), 'symmetric'],
     [L12n2176(-1,-4)(-4,1)(3,2)(-5,-2), 'symmetric'],
     [L12n2176(-1,-4)(-4,1)(5,2)(-3,-2), 'symmetric'],
     [L14n62837(-4,1)(0,-1)(-1,2)(0,-1), 'symmetric'],
     [L14n62837(5,1)(0,1)(-1,-2)(-4,-1), 'symmetric'],
     [L14n62837(-1,-2)(-9,-5)(2,1)(5,2), 'symmetric'],
     [L14n62837(1,2)(9,4)(-2,1)(-3,-2), 'symmetric'],
     [L14n62837(-1,-2)(-9,-4)(2,-1)(3,2), 'symmetric'],
     [L14n62837(1,2)(9,5)(-2,-1)(-5,-2), 'symmetric'],
     [L14n62837(-5,-1)(0,-1)(1,2)(4,1), 'symmetric'],
     [L14n62837(4,-1)(0,1)(1,-2)(0,1), 'symmetric']]

    New triangulation received from PLink!

All surgery diagrams are strongly invertible. We build the quotients by hand. For L13n9734 it is not hard to find the branching set. It has the following PD code:

In [58]:

    B=snappy.Link([(8, 37, 9, 10), (2, 16, 3, 15), (7, 16, 8, 17), (17, 6, 18, 7), (3, 20, 4, 21), (23, 12, 24, 13), (11, 24, 12, 25), (25, 10, 26, 11), (9, 27, 0, 26), (27, 1, 28, 0), (13, 28, 14, 29), (22, 30, 23, 29), (30, 5, 31, 6), (31, 19, 32, 18), (4, 33, 5, 34), (19, 33, 20, 32), (36, 1, 37, 2), (14, 36, 15, 35), (21, 34, 22, 35)]
    )
    B

Out[58]:

    <Link: 2 comp; 19 cross>

First we check that it is the correct DBC:

In [59]:

    double_branched_cover(B.exterior()).is_isometric_to(K)

Out[59]:

    True

In [60]:

    B.exterior().volume(verified=True)

Out[60]:

    12.1514600187?

In [61]:

    B.jones_polynomial()

Out[61]:

    q^-19 - 2*q^-17 + 3*q^-15 - 3*q^-13 + 3*q^-11 - 2*q^-9 + q^-7 - q^-3 + 2*q^-1

In [62]:

    B.simplify('global')
    B

Out[62]:

    <Link: 2 comp; 16 cross>

In [63]:

    print(B.braid_word())

    [-1, -2, 3, 4, 3, -5, 4, 3, 2, 4, 1, 3, -5, 2, 4, 5, 6, 5, -7, -6, 5, -4, 3, -5, -4, -5, 6, -5, 7, 4, -3, -2, -3, 4, -5, -6]

For the second branching set we take the surgery description from [Baker--Kegel 2022] and build the quotient by hand. We get a branching set with the following PD code:

In [39]:

    B=snappy.Link([(5, 0, 6, 1), (11, 6, 0, 7), (10, 30, 11, 29), (28, 10, 29, 9), (21, 2, 22, 3), (22, 31, 23, 32), (1, 20, 2, 21), (30, 19, 31, 20), (23, 19, 24, 18), (17, 25, 18, 24), (25, 17, 26, 16), (15, 27, 16, 26), (27, 15, 28, 14), (13, 8, 14, 9), (7, 12, 8, 13), (33, 4, 12, 5), (3, 32, 4, 33)])
    B

Out[39]:

    <Link: 2 comp; 17 cross>

First we check that it is the correct DBC:

In [41]:

    double_branched_cover(B.exterior()).is_isometric_to(K)

Out[41]:

    True

In [40]:

    B.exterior().volume(verified=True)

Out[40]:

    12.1514600187?

So it appears to have the same volume. However the Jones polynomial is different:

In [42]:

    B.jones_polynomial()

Out[42]:

    2*q^-11 - q^-9 + q^-5 - 2*q^-3 + 3*q^-1 - 3*q + 3*q^3 - 2*q^5 + q^7

In [43]:

    B.simplify('global')
    B

Out[43]:

    <Link: 2 comp; 16 cross>

In [45]:

    print(B.braid_word())

    [-1, 2, 3, -4, 5, -6, 7, 8, -9, -8, -7, 6, 5, 4, -3, -2, 1, 3, -4, 5, -6, 5, 7, 4, 8, -3, -5, 9, 2, 6, -5, 7, -4, -6, 3, -5, -6, -5, -4, -5, -6, -7, -6, -8, -5, -7, 4, -3, 5, -2, 6, 5, 4, -5]

It remains to check that they both are not KH thin. For that we build the alphabetical braid words and use Knot Job to compute the KH.

In [64]:

    import string

    def braid_word_to_letters(word):
        '''Returns an alphabetical describtion of the braid word.'''
        upper=list(string.ascii_uppercase)
        lower=list(string.ascii_lowercase)
        stringword=''
        for x in word:
            for letter in lower:
                if x==ord(letter) - 96:
                    stringword=stringword+letter
                    break
            for letter in upper:
                if -x==ord(letter) - 64:
                    stringword=stringword+letter
                    break
        return stringword

In [65]:

    braid_word_to_letters([-1, -2, 3, 4, 3, -5, 4, 3, 2, 4, 1, 3, -5, 2, 4, 5, 6, 5, -7, -6, 5, -4, 3, -5, -4, -5, 6, -5, 7, 4, -3, -2, -3, 4, -5, -6])

Out[65]:

    'ABcdcEdcbdacEbdefeGFeDcEDEfEgdCBCdEF'

In [66]:

    braid_word_to_letters([-1, 2, 3, -4, 5, -6, 7, 8, -9, -8, -7, 6, 5, 4, -3, -2, 1, 3, -4, 5, -6, 5, 7, 4, 8, -3, -5, 9, 2, 6, -5, 7, -4, -6, 3, -5, -6, -5, -4, -5, -6, -7, -6, -8, -5, -7, 4, -3, 5, -2, 6, 5, 4, -5])

Out[66]:

    'AbcDeFghIHGfedCBacDeFegdhCEibfEgDFcEFEDEFGFHEGdCeBfedE'